# 页面分析器：
# 使用脚本提取内容
# by lw 2023-11-8

import requests
import json
from analysiser.html_analyzer import HTMLAnalyzer
import os


def process_single_quotes(obj):
    """
    处理json单引号问题

    Args:
        obj (_type_): json对象

    Returns:
        _type_: json对象
    """
    for key, value in obj.items():
        if isinstance(value, str):
            obj[key] = value.replace("'", "\\'")
    return obj


class PageAnalyzer:

    def __init__(self, url=None):
        if url:
            self.url = url  # 要分析的页面url
        self.html_content = ""  # 页面的html
        self.script = ""  # 页面解析的脚本
        self.html_analyzer = HTMLAnalyzer()  # html内容提取器

    def get_url(self) -> str:
        """
        获取分析页面的url

        Returns:
            str: 分析页面的url
        """
        return self.url

    def set_url(self, url: str):
        """
        获取分析页面的url

        Returns:
            str: 分析页面的url
        """
        self.url = url
        self.load_page(self.url)

    def load_page(self, url=None) -> bool:
        """
        加载页面内容

        Returns:
            bool: 加载成功返回True，否则返回False
        """
        if url:
            self.url = url  # 要分析的页面url
        if not self.url:
            print('请指定要加载的页面！')
            return False
        response = requests.get(self.url)
        self.html_content = response.content
        if response.status_code == 200:
            return True
        else:
            return False

    def load_html(self, html_content: str) -> bool:
        """
        加载HTML内容

        Args:
            html_content (str): HTML内容

        Returns:
            bool: 加载成功返回True，否则返回False
        """
        self.html_content = html_content
        return True

    def load_script(self, jstr: str) -> bool:
        """
        加载解析脚本

        Args:
            jstr (str): 解析脚本的JSON字符串

        Returns:
            bool: 加载成功返回True，否则返回False
        """
        self.script = json.loads(jstr, object_hook=process_single_quotes)
        return True

    def load_json_script(self, jobject: json) -> bool:
        """
        加载解析脚本(json对象)

        Args:
            jstr (json): 解析脚本的JSON对象

        Returns:
            bool: 加载成功返回True，否则返回False
        """
        self.script = jobject
        return True

    def read_script(self, filename: str) -> bool:
        """
        从文件中读取解析脚本

        Args:
            filename (str): 解析脚本文件名

        Returns:
            bool: 读取成功返回True，否则返回False
        """
        try:
            with open(filename, 'r', encoding='utf-8') as file:
                self.script = json.load(file)
                # print("成功加载脚本:", self.script)
                return True
        except FileNotFoundError:
            print("文件未找到")
            return False
        except json.JSONDecodeError:
            print("JSON 文件解析错误")
            return False

    def analyze(self) -> dict:
        """
        分析页面内容

        Returns:
            dict: 分析结果
        """
        if not self.html_content:
            if not self.load_page():
                print("无法加载页面！")
                return None
        if not self.script:
            print("请加载页面解析脚本！")
            return None
        result = {}
        self.html_analyzer.load_html(self.html_content)

        # 逐个解析script定义的一个一个规则，形成dict结果数据
        for rule in self.script:
            key = rule['key']
            value = self.get_entity(rule)
            if value is not None:
                result[key] = value
        return result

    def get_entity(self, rule: dict):
        """
        获取一个规则rule定义的搜索架构获得的值

        Args:
            rule (dict): 搜索规则，请参见“/page_analy_script/readme.md”

        Returns:
            _type_: str|[]|{}
        """
        if 'type' not in rule:
            return "无效的数据类型"
        if rule['type'] == 'simple':
            return self.simple_ent(rule)
        elif rule['type'] == 'complex':
            return self.complex_ent(rule)
        elif rule['type'] == 'table':
            return self.table_ent(rule)
        elif rule['type'] == 'loop':
            return self.loop_ent(rule)
        else:
            return "无效的数据类型"
        # 下面的写法好像更慢：
        # switcher = {
        #     'simple': self.simple_ent(rule),
        #     'complex': self.complex_ent(rule),
        #     'table': self.table_ent(rule),
        #     'loop': self.loop_ent(rule)
        # }
        # return switcher.get(rule['type'], "无效的数据类型")

    def simple_ent(self, rule: dict) -> str:
        """
        简单关键词内容搜索

        Args:
            rule (dict): 该简单体搜索规则，请参见“/page_analy_script/readme.md”

        Returns:
            str: 匹配的内容
        """

        result = ""
        if 'analysiser' in rule:
            analysiser = rule['analysiser']  # 搜索规则列表
            for arule in analysiser:
                keyword = arule.get('keyword', '')
                content_reg = arule.get('content_reg', '')
                locate = arule.get('locate', {})
                find = arule.get('find', '')
                xpath = arule.get('xpath', '')

                if locate != {}:
                    if keyword != '':
                        result = self.html_analyzer.find_keyword_content(
                            keyword=keyword,
                            reg=content_reg,
                            parent_desc=locate)
                    else:
                        result = self.html_analyzer.find(tag_desc=locate,
                                                         reg=content_reg)
                elif find != '':
                    result = self.html_analyzer.find(tag_desc=locate,
                                                     reg=content_reg)
                elif xpath != '':
                    result = self.html_analyzer.xextract_keyword_content(
                        keyword=keyword, path=xpath, reg=content_reg)
                if result != "":
                    return result
        return result

    def complex_ent(self, rule: dict) -> dict:
        """
        递归搜索内容（结构体使用这个接口）

        Args:
            rule (dict): 该复合体搜索规则，请参见“/page_analy_script/readme.md”

        Returns:
            dict: 匹配的内容
        """
        result = {}
        if 'rules' in rule:
            for sub_rule in rule['rules']:
                key = sub_rule['key']
                value = self.get_entity(sub_rule)
                if value is not None:
                    result[key] = value
        return result

    def table_ent(self, rule: dict) -> []:
        """
        获取表内容

        Args:
            rule (dict): 该表格数据体搜索规则，请参见“/page_analy_script/readme.md”

        Returns:
            []: 提取的表格，返回键值对数组格式
            
        """
        if 'header_map' not in rule:
            return []
        header_map = rule['header_map']
        locate = ""
        if 'locate' in rule:
            locate = rule['locate']
        return self.html_analyzer.extract_table(header_map, parent_desc=locate)

    def loop_ent(self, rule: dict) -> []:
        """
        循环结构中获取实体数组

        Args:
            rule (dict): 该表格数据体搜索规则，请参见“/page_analy_script/readme.md”

        Returns:
            []: 提取的循环结构中的实体数组
            
        """
        if 'container' not in rule:
            return []
        if 'part' not in rule:
            return []

        # 找到容器的box：
        container = self.html_analyzer.locate_box(rule['container'])
        if not container:
            return []
        # 循环体：
        parts = self.html_analyzer.find_all_subbox(container, rule['part'])

        if 'rules' not in rule:
            return []
        result = []
        html_bak = self.html_content  #保护现场
        for part in parts:
            self.load_html(part.prettify())  # 加载循环体部分的html
            self.html_analyzer.load_html(part.prettify())  # 设置HTML解析器
            item = {}
            for sub_rule in rule['rules']:
                key = sub_rule['key']
                value = self.get_entity(sub_rule)
                if value is not None:
                    item[key] = value
            result.append(item)
        self.load_html(html_bak)  # 恢复现场
        self.html_analyzer.load_html(html_bak)  # 设置HTML解析器

        return result


# 示例用法
if __name__ == '__main__':
    html = '''
    <body>
        <div class="clearfix" id="newsText">
            <div>
                <div class="protect" id="noticeArea">
                    <meta http-equiv="content-type" content="text/html;charset=utf-8">
                    <p data-v-6b13d514=""></p>
                    <h4><strong>一、项目编号：N5100012022001470</strong>asdfadsaf</h4>
                    <h4><strong>二、项目名称：西南区域环境空气质量预测预报中心能力提升项目</strong></h4>
                    <h4><strong>三、采购结果</strong></h4>
                    <div class="noticeBidResult-noticeBidResult _notice_content_noticeBidResult-noticeBidResult supplier dynamic-form-editor"
                        id="_notice_content_noticeBidResult-noticeBidResult">
                        <div>
                            <p class="u-content">合同包1:</p>
                            <table width="100%" border="1" cellspacing="0">
                                <thead>
                                    <tr style="height:36.4pt">
                                        <th>供应商名称</th>
                                        <th>供应商地址</th>
                                        <th>中标（成交）金额</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr>
                                        <td style="width:400px;word-break:break-all;">四川省国土空间生态修复与地质灾害防治研究院</td>
                                        <td>成都市人民北路一段25号</td>
                                        <td class="alignright"
                                            style="width:200px;text-align: right;white-space:nowrap;">
                                            <span>22,390,000.00元</span>
                                        </td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                        <div>
                            <p class="u-content">合同包2:</p>
                            <table width="100%" border="1" cellspacing="0">
                                <thead>
                                    <tr style="height:36.4pt">
                                        <th>供应商名称</th>
                                        <th>供应商地址</th>
                                        <th>中标（成交）金额</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr>
                                        <td style="width:400px;word-break:break-all;">北京东方至远科技股份有限公司</td>
                                        <td>北京市海淀区花园北路14号66幢2层207号</td>
                                        <td class="alignright"
                                            style="width:200px;text-align: right;white-space:nowrap;">
                                            <span>2,692,128.00元</span>
                                        </td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                        <div>
                            <p class="u-content">合同包3:</p>
                            <table width="100%" border="1" cellspacing="0">
                                <thead>
                                    <tr style="height:36.4pt">
                                        <th>供应商名称</th>
                                        <th>供应商地址</th>
                                        <th>中标（成交）金额</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr>
                                        <td style="width:400px;word-break:break-all;">成都理工大学</td>
                                        <td>成都市二仙桥东三路1号</td>
                                        <td class="alignright"
                                            style="width:200px;text-align: right;white-space:nowrap;">
                                            <span>1,170,200.00元</span>
                                        </td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                    </div>
                    <h4><strong>九、凡对本次公告内容提出询问，请按以下方式联系。</h4></strong>
                    <div class="innercontent">
                        <h6>1.采购人信息</h6>
                        <p>名称：<span
                                class="u-content noticePurchase-purchaserOrgName _notice_content_noticePurchase-purchaserOrgName dynamic-form-editor"
                                id="_notice_content_noticePurchase-purchaserOrgName">四川省自然资源厅</span>
                        </p>
                        <p>地址：<span
                                class="u-content noticePurchase-purchaserOrgAddress _notice_content_noticePurchase-purchaserOrgAddress dynamic-form-editor"
                                id="_notice_content_noticePurchase-purchaserOrgAddress">成都市青羊区百卉路4号</span>
                        </p>
                        <p>联系方式：<span
                                class="u-content noticePurchase-purchaserLinkTel _notice_content_noticePurchase-purchaserLinkTel dynamic-form-editor"
                                id="_notice_content_noticePurchase-purchaserLinkTel">028-87036212</span>
                        </p>
                    </div>
                </div>
            </div>
        </div>
    </body>
    '''
    url = 'https://ggzyjy.sc.gov.cn/cyzdxm/019002/019002002/20221110/8a69ce88845d0ce301846087352e087eCY.html'
    url = 'https://ggzyjy.sc.gov.cn/jyxx/002002/002002003/20221215/8a69d1ff84f4f1e8018514d8c88e6275.html'
    # 四川公共资源网站的中标结果公告页面的提取脚本：
    script_filename = os.path.join(os.path.dirname(__file__), '..',
                                   'page_analy_script', 'script_PR_SC.json')
    analyzer = PageAnalyzer(url)
    # analyzer.load_html(html)
    analyzer.read_script(script_filename)
    result = analyzer.analyze()
    print(result)
